#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 时间     : 2021-04-15 19:23
# 作者     : DL (584054432@qq.com)
# 网址     : https://gitee.com/dingliangPython/
# 软件版本 : Python3.7.3
# 功能     ：
# cython: language_level=3


import logging
import os
import re
import sys
import time
import datetime
import xml.dom.minidom
import csv
import uuid
import s7
import threading
import queue

import database_client
import timeEXT
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED
from apscheduler.executors.pool import ProcessPoolExecutor, ThreadPoolExecutor
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QDateTime, Qt, QThread
from PyQt5.QtGui import QBrush, QColor
from PyQt5.QtWidgets import (QCheckBox, QDialog, QFileDialog,
                             QGridLayout, QHeaderView, QMainWindow,
                             QPushButton, QTableWidgetItem, QTextBrowser)
from pyqt5_common import APP_CONFIG_PATH, APP_NAME, APP_SCRIPTS_PATH, check_cpu_memory, check_local_db_password, \
    error_info, LOG_COLOR, password_check, show_message, show_window_top, SystemTray, APP_MODULES_PATH, get_log_color
import log_error
import config_window
import main_ui
import map_window

# from memory_profiler import profile
# 添加环境变量

sys.path.append(APP_SCRIPTS_PATH)
sys.path.append(APP_MODULES_PATH)

MODULES = dir()
# 异常处理类
log_error.ExceptHookHandler(mainFrame = True)


class MainWindow(QMainWindow):
    """docstring for MainWindow"""
    # 接收日志信号
    log_signal = pyqtSignal(str)
    item_signal_config = None
    add_task_item_signal = pyqtSignal(int, dict)
    serial_number_signal = pyqtSignal(str, str)
    number_count_signal = pyqtSignal(int)
    task_rt_value_signal = pyqtSignal([dict], [list])
    task_rt_value = {}
    s7_rt_value = {}

    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = main_ui.Ui_MainWindow()
        self.ui.setupUi(self)

        self.config_window = config_window.Window(self.log_signal)
        self.setWindowTitle(self.config_window.ui.APPName.text())
        self.config_window.app_name_signal.connect(self.setWindowTitle)
        self.config_window.s7_rt_value_signal.connect(self.read_s7_rt_value)
        self.local_connect = self.config_window.loacl_connect

        self.log = logging.getLogger('main.ui')
        self.log_signal.connect(self.log_callback)
        self.log_view = QTextBrowser()
        # 设置行宽
        self.ui.log_view.setColumnWidth(0, 130)
        self.ui.log_view.setColumnWidth(1, 60)
        self.ui.log_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
        self.ui.log_view.itemDoubleClicked.connect(self.open_log_file)
        self.logWindow = self.init_log_widget(self.log_view)

        # 数据库连接
        self.conn = self.get_connect()

        self.task_config_window = map_window.Window(self.local_connect, self.conn)

        self.ui.dockWidget.hide()

        self.ui.menu_config.triggered.connect(self.show_config_window)
        self.ui.menu_open_log.triggered.connect(self.open_log_file)
        self.ui.menu_mapping_config.triggered.connect(self.show_task_config_window)

        # 时间初始
        self.ui.begin_time.setDateTime(
                QDateTime.fromString(time.strftime('%Y-%m-%d') + ' 00:00:00', 'yyyy-MM-dd hh:mm:ss'))
        self.ui.ent_time.setDateTime(
                QDateTime.fromString(time.strftime('%Y-%m-%d') + ' 23:59:59', 'yyyy-MM-dd hh:mm:ss'))

        # 初始任务显示
        self.init_task_show()
        # 实例调度
        self.scheduler = BackgroundScheduler(executors = {
                'default': ThreadPoolExecutor(20),
                'processpool': ProcessPoolExecutor(5)
                })

        # 启动调度
        self.scheduler.start()
        # 任务异常监听
        self.scheduler.add_listener(self.task_error, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
        # # 增加检测任务
        self.scheduler.add_job(self.task_check, IntervalTrigger(seconds = 3), id = 'check_task_exist', coalesce = True,
                               replace_existing = True)

        # 增加动态检测新增脚本任务
        self.scheduler.add_job(self.script_task_save, IntervalTrigger(seconds = 5), id = 'script_task_save',
                               coalesce = True, replace_existing = True)
        # 增加检测内存任务
        self.scheduler.add_job(check_cpu_memory, IntervalTrigger(seconds = 1200), args = (50, 2),
                               id = 'cpu_memory_check',
                               coalesce = True, replace_existing = True)
        # 接收动态脚本任务添加信号
        self.add_task_item_signal.connect(self.set_widget)
        self.serial_number_signal.connect(self.set_serial_number)
        self.number_count_signal.connect(lambda v: self.ui.query_task_data_count.setValue(v))

        self.task_rt_value_signal[dict].connect(self.show_task_rt)
        self.task_rt_value_signal[list].connect(self.show_task_rt)

        # 托盘
        self.SystemTray = SystemTray(self, self.config_window.password_enable)

    # 筛选数据
    def on_filter_clicked(self):
        obj = self.ui.data_table
        value = self.ui.filter_value.text()
        if not value: return
        rows = obj.rowCount()
        cols = obj.columnCount()
        if not rows: return
        items = obj.findItems(value, Qt.MatchContains )
        items = obj.findItems(value, Qt.MatchRegExp) if not items else items
        if not items:
            show_message("在当前查询的数据内没有找到：{}".format(value))
            return
        row_list = [item.row() for item in items]
        self.ui.query_task_data_count.setValue(len(row_list))
        data = [[obj.item(row, col).text() if obj.item(row, col) else "" for col in range(cols) if col > 0] for row in
                row_list]
        if not data: return

        _ = [obj.removeRow(i) for i in range(rows, -1, -1) if i not in row_list]

    # 存储s7数据
    def read_s7_rt_value(self, _dict):
        self.s7_rt_value.update(_dict)

    # 显示任务实时数据窗口
    @pyqtSlot(int)
    def on_show_rt_start_stateChanged(self, value):
        if value:
            self.ui.dockWidget.setFixedWidth(self.width())
            self.ui.dockWidget.setFloating(True)
            self.ui.dockWidget.show()
        else:
            self.ui.dockWidget.hide()

    # 显示实时数据
    def show_task_rt(self, data):
        self.task_rt_value.update(data)
        obj = self.ui.rt_table
        obj.clear()
        rows = len(data)
        if isinstance(data, list):
            value_list = []
            for i in range(rows):
                row = data[i]
                value_list.append(list(row.keys()))
                value_list.append(list(row.values()))
            for i, values in enumerate(value_list):
                obj.setColumnCount(len(values))
                for col, v in enumerate(values):
                    obj.setItem(i, col, QTableWidgetItem(str(v)))

            return
        if isinstance(data, dict):
            head = ['名称', '值']
            obj.setHorizontalHeaderLabels(head)
            obj.setRowCount(rows)
            obj.setColumnCount(2)
            i = 0
            for key, v in data.items():
                obj.setItem(i, 0, QTableWidgetItem(key or ''))
                obj.setItem(i, 1, QTableWidgetItem(str(v) or ''))
                i += 1
            return

    # 显示脚本任务实时数据
    def showData(self, data):
        if not data:
            return

        self.task_rt_value_signal.emit(data)

    # 设置显示流水号
    def set_serial_number(self, task_name, serial_number):
        number = self.ui.serial_number.text()
        name, cur_name = self.get_task_name()
        if task_name == cur_name and serial_number != number:
            self.ui.serial_number.setText(serial_number)

    # 获取选择的任务名称
    def get_task_name(self):
        name = self.ui.select_task_list.currentText()
        items = self.ui.task_table.findItems(name, Qt.MatchContains | Qt.MatchRecursive)
        if not items:
            return None, None
        task_name = self.ui.task_table.item(items[0].row(), 1).text()
        return name, task_name

    # 修改流水号
    @check_local_db_password()
    def on_serial_number_editingFinished(self, *args):

        name, task_name = self.get_task_name()
        number = self.ui.serial_number.text()
        info = ('  *   请确认修改任务: {} ,流水id= {}\n'
                '  **  修改流水,将重传大于修改的id数据\n'
                '  **  如已上传数据有可能无法再上传,导致阻塞\n'
                ).format(task_name, number)
        if task_name and number and show_message(info):
            sql = "UPDATE LastMark SET Mark='{}' WHERE TaskName='{}'".format(number, task_name)
            self.local_connect.execSql(sql)
            self.log.debug("执行:{}".format(sql))
            self.log.warning('任务:{},手动修改最后流水号:{}'.format(name, number))

    # 没有的值返回None
    def __getattr__(self, item):
        return None

    # 任务异常
    def task_error(self, event):
        if event.exception or event.traceback:
            # path = os.path.join(os.path.abspath('./'), 'Scripts',)
            path = os.path.join(os.path.splitext(sys.argv[0])[0], 'Scripts', event.job_id)
            error_msg = event.traceback.replace("<string>", path)
            self.log.error("任务：{},异常日志：{}".format(event.job_id, error_msg))

    # 任务单元格变化,调度时间修改
    def item_changed(self, item):
        row = item.row()
        col = item.column()
        value = item.text()
        if col != 2:
            return
        task_name = self.ui.task_table.item(row, 1).text()
        sql = "UPDATE SchedulerConfig SET Cycle='{}' WHERE Name='{}'".format(value, task_name)
        self.local_connect.execSql(sql)
        self.log.debug("执行:{}".format(sql))
        # 修改调度时间
        if self.__dict__.get('scheduler') and self.scheduler.get_job(task_name) and value:
            self.scheduler.modify_job(task_name, trigger = IntervalTrigger(seconds = int(value)))
            self.log.warning("修改任务= {} ,调度周期: {} 秒....".format(task_name, value))

    # 自动选择
    def cell_widget_changed(self, row, value):
        task_name = self.ui.task_table.item(row, 1).text()
        sql = "UPDATE SchedulerConfig SET AutoStart='{}' WHERE Name='{}'".format(2 if value else 0, task_name)
        self.local_connect.execSql(sql)
        self.log.debug("执行:{}".format(sql))
        self.log.warning("修改任务= {} ,启动自动运行: {} ,软件重启生效".format(task_name, value))

    # 开始停止移除
    @error_info
    def pushbutton_clicked(self, row, col):
        task_name = self.ui.task_table.item(row, 1).text()
        # 检测存在调度
        if not self.scheduler.get_job(task_name):
            return
        # 启动列
        if col == 4:
            self.scheduler.get_job(task_name).resume()
            self.set_widget_color(self.ui.task_table.cellWidget(row, col), 1)
            self.set_widget_color(self.ui.task_table.cellWidget(row, col + 1), 0)
            self.log.warning("修改任务= {} ,启动任务 ".format(task_name))
            return
        # 停止列
        if col == 5:
            self.scheduler.get_job(task_name).pause()
            self.set_widget_color(self.ui.task_table.cellWidget(row, col), 1)
            self.set_widget_color(self.ui.task_table.cellWidget(row, col - 1), 0)
            self.log.warning("修改任务= {} ,停止任务 ".format(task_name))
            return
        # 删除
        if col == 6:
            if self.config_window.password_enable and not password_check():
                return
            info = ("*  请确认是否一定要删除第{}行的任务: {} ?\n\n"
                    "*  脚本任务,删除后会自动创建,可以停止任务或删除脚本!\n\n"
                    "** 扫描任务,删除后需要配置增加!\n\n"
                    "*  其它任务,删除后,重启程序会自动创建!"
                    "** 多个任务移除,从下向上依次移除,或每移除一次重启程序"
                    ).format(row + 1, task_name)
            if not show_message(info):
                return
            self.log.warning("修改任务= {} ,删除任务 ".format(task_name))
            sql = "DELETE FROM SchedulerConfig WHERE Name='{}'".format(task_name)
            self.local_connect.execSql(sql)
            self.log.debug("执行:{}".format(sql))
            self.ui.task_table.removeRow(row)
            self.scheduler.remove_job(task_name)
            if show_message("删除调度任务:{},需要重启程序,是否退出程序?".format(task_name)):
                os._exit(0)

    # 手动上传
    @pyqtSlot(bool)
    def on_manual_upload_data_clicked(self):
        name, task_name = self.get_task_name()
        task_conifg = self.task_config_window.task_config.get(task_name)
        _, _, mode = task_name.split('>')
        mode = int(mode)
        obj = self.ui.data_table
        rows = obj.rowCount()
        cols = obj.columnCount()
        if not rows:
            show_message("先查询需要上传数据")
            return
        select_list = [i for i in range(rows) if obj.cellWidget(i, 0).isChecked()]
        if not select_list:
            show_message("请先选择要上传的数据")
            return
        data = [[obj.item(row, col).text() if obj.item(row, col) else '' for col in range(cols) if col > 0] for row in
                select_list]
        result = self.exec_task(data, task_conifg, mode, 'manual')
        if result:
            self.log.info('任务:{},手动上传:{}行的数据成功'.format(name, list(map(lambda v: v + 1, select_list))))
            _ = [[self.set_widget_color(obj.item(i, col), '20') for col in range(cols)] for i in select_list]
        else:
            _ = [[self.set_widget_color(obj.item(i, col), '30') for col in range(cols)] for i in select_list]
        self.on_data_all_deselect_clicked()

    # 手动查询
    @pyqtSlot(bool)
    def on_query_task_data_clicked(self):
        name, task_name = self.get_task_name()
        obj = self.ui.data_table
        obj.clear()
        begin_time = self.ui.begin_time.text()
        end_time = self.ui.ent_time.text()
        task_config = self.task_config_window.task_config.get(task_name)
        if not task_config:
            self.log.warning("没有找到任务:{},的配置...".format(name))
            self.number_count_signal.emit(0)
            return
        sql, source_conn = task_config.get('manual_select_sql')
        sql = sql.format(begin_time, end_time)
        data = source_conn.loadSql(sql)
        self.log.debug("任务:{},手动查询:{}".format(name, sql))
        if not data:
            self.log.info("任务:{},手动未查询到数据,请重新选择时间范围....".format(name))
            self.ui.data_table.setRowCount(0)
            return

        head_value = ['选择'] + task_config.get('name_list')
        cols = len(head_value)
        rows = len(data)
        self.number_count_signal.emit(rows)
        obj.setRowCount(rows)
        obj.setColumnCount(cols)
        obj.setColumnWidth(0, 30)
        obj.setHorizontalHeaderLabels(head_value)
        # 显示数据
        for row, values in enumerate(data):
            for col in range(cols):
                if col == 0:
                    cell = QCheckBox("")
                    obj.setCellWidget(row, col, cell)
                    continue
                value = str(values[col - 1]) if values[col - 1] else ""
                item = QTableWidgetItem(value)
                obj.setItem(row, col, item)

    # 全选手动查询数据
    @pyqtSlot(bool)
    def on_data_all_select_clicked(self):
        row_count = self.ui.data_table.rowCount()
        if not row_count:
            return
        if row_count:
            _ = [self.ui.data_table.cellWidget(i, 0).setCheckState(2) for i in range(row_count)]

    # 取消手动选择数据
    @pyqtSlot(bool)
    def on_data_all_deselect_clicked(self):
        rows = self.ui.data_table.rowCount()
        if rows:
            _ = [self.ui.data_table.cellWidget(i, 0).setCheckState(0) for i in range(rows)]

    # 动态存储脚本到任务表
    def script_task_save(self):
        path = os.path.join(APP_CONFIG_PATH, APP_NAME, 'scripts')
        # xx.3s.py 类式脚本文件
        scripts_files = [file for file in os.listdir(path) if
                         os.path.isfile(os.path.join(path, file)) and re.findall('task.py$', file, re.I)]
        if not scripts_files:
            return
        for i, task_name in enumerate(scripts_files):
            result = self.task_config_window.get_task_config(task_name)
            if result:
                continue
            # 新增的脚本任务保存到数据库
            name = '脚本任务:{}'.format(i + 1)
            sql = ("INSERT INTO SchedulerConfig (Description,Name,Cycle,AutoStart)"
                   " VALUES ('{}','{}',5,2)").format(name, task_name)
            self.log.info("新增加脚本任务: {},{}".format(task_name, sql))
            self.local_connect.execSql(sql)
            self.log.debug("执行:{}".format(sql))

    # 获取连接
    def get_connect(self):
        # 初始化数据库连接
        data = self.local_connect.loadSql("SELECT * FROM ConnectConfig ", 1)
        if data:
            conn = database_client.get_connect_dict(data)
            return conn

    # 初始任务显示
    def init_task_show(self):
        # 任务列表
        data = self.task_config_window.get_task_config()
        if not data:
            self.log.error("没有找到任务,请检测是否增加调度任务或./scripts目录是否存在脚本文件?")
            return
        self.ui.task_table.setRowCount(len(data))
        for i, width in enumerate([250, 280, 70, 80, 100, 100, 80]):
            self.ui.task_table.setColumnWidth(i, width)
        for i, row in enumerate(data):
            self.set_widget(i, row)

    # 检查任务
    # @error_info
    def task_check(self):
        # 获取存储任务配置
        task_data = self.task_config_window.get_task_config()
        if not task_data:
            return
        obj = self.ui.task_table
        # 检查任务是否在调度列表
        for row in task_data:
            task_name = row.get('Name')
            rows = obj.rowCount()
            task_name_list = [obj.item(i, 1).text() for i in range(rows) if obj.item(i, 1)]
            # 不在时增加到调度表
            if task_name not in task_name_list:
                obj.insertRow(obj.rowCount())
                count = obj.rowCount()
                self.add_task_item_signal.emit(count - 1, row)

        # 检测任务是否为自动运行,自动运行启动任务
        for i in range(obj.rowCount()):
            task_obj = obj.item(i, 1)
            if not task_obj:
                continue
            task_name = task_obj.text()
            task_status = obj.cellWidget(i, 3).isChecked()
            task_cycle = int(obj.item(i, 2).text())
            task = self.scheduler.get_job(task_name)
            if task:
                continue
            if '.py' in task_name:
                func = self.run_task
            else:
                func = self.cycle_task
            # 不在时增加任务调度
            self.scheduler.add_job(
                    func,
                    IntervalTrigger(seconds = task_cycle),
                    args = (task_name, task_cycle),
                    id = task_name,
                    replace_existing = True,
                    coalesce = True
                    )
            start = obj.cellWidget(i, 4)
            stop = obj.cellWidget(i, 5)
            if task_status:
                self.set_widget_color(start, 1)
                self.set_widget_color(stop, 0)
            else:  # 非自动暂停
                self.scheduler.get_job(task_name).pause()
                self.set_widget_color(start, 0)
                self.set_widget_color(stop, 1)

    # 执行任务处理数据

    def exec_task(self, data, task_config, mode, task_type = 'auto'):
        name = task_config.get('name')
        flag_field = task_config.get('flag_field')
        index = task_config.get('source_field_list').index(task_config.get('source_gid_field'))
        values = self.parse_data(data, task_config.get('field_type'))
        ids = ','.join(["'{}'".format(row[index]) for row in data])
        # 执行,清理
        clear = task_config.get('clear_sql')
        if clear and mode in range(5, 10):
            sql, conn = clear
            sql = sql.format(ids)
            self.log.info("覆盖插入,先清除数据:{}".format(sql))
            conn.execSql(sql)

        # 执行
        sql, conn = task_config.get('exec_sql')
        # 更新拿出主键
        if mode in range(10, 15):
            sql = sql.format(ids)
            values = [list(row) for row in values]
            _ = [row.pop(index) for row in values]
            values = [tuple(row) for row in values]
            result = conn.execSql(sql, values)

        else:
            result = conn.execSql(sql, values)

        self.log.debug("执行:{},{}".format(sql, values))
        if not result:
            self.log.warning("任务:{},处理数据失败!".format(name))
            return
        self.log.info("任务: {} ,处理数据成功, sql= {},values = {}".format(name, sql, values))

        if task_type != 'auto':
            self.log.info("手动运行任务:{},不更新标记...".format(name))
            return result
        # 更新标记
        if mode > 15:
            return
        sql, conn = task_config.get('flag_sql')
        values = [(row[index],) for row in data]

        if not flag_field:  # 非标记字段查询最后流水号
            values = [row[index] for row in data]
            values.sort()
            gid = values[-1]
            sql = sql.format(gid)
            result = conn.execSql(sql)
            self.log.debug("执行:{}".format(sql))
        else:
            # mssql =2000 数据是2000不能批量处理
            if conn.config.get('mssql_version') == '2000' and conn.type.lower() in ('sql server', 'sqlserver', 'mssql'):
                values = ",".join(["'{}'".format(row[index]) for row in data])
                sql, conn = task_config.get('flag_sql')
                sql = sql.replace('=%s', " IN ({})".format(values))
                result = conn.execSql(sql)
            else:
                result = conn.execSql(sql, values)
            self.log.debug("执行:{}，{}".format(sql, values))

        if not result:
            self.log.warning("处理标记失败,sql={}".format(sql))
            return
        self.log.info("任务: {} ,处理数据标记成功,sql ={},value= {}".format(name, sql, values))

    # 执行调度任务
    def cycle_task(self, task_name, sec):
        time_begin = time.process_time()
        source_table, target_table, mode = task_name.split('>')
        mode = int(mode)
        # 任务配置
        task_config = self.task_config_window.task_config.get(task_name)
        if not task_config:
            self.log.warning("请检测数据库连接是否正确,任务:{},调度失败...".format(task_name))
            return

        flag_field = task_config.get('flag_field')
        if not flag_field:
            # 设置流水号显示
            last_gid = self.task_config_window.get_last_gid(task_name)
            self.serial_number_signal.emit(task_name, str(last_gid))

        name = task_config.get('name')
        # 查询
        sql, conn = task_config.get('select_sql')
        if not flag_field:
            sql = sql.format(last_gid)
        sql = sql.replace("=IS NOT NULL", ' IS NOT NULL ')
        sql = sql.replace("=IS NULL", ' IS NULL ')
        data = conn.loadSql(sql)
        self.log.debug("任务:{},查询数据:{}".format(name, sql))
        if not data:
            self.log.debug("任务:{},没有待处理的数据".format(name))
            return

        self.exec_task(data, task_config, mode)

        time_end = time.process_time()
        dff_time = time_end - time_begin
        self.log.debug("当前任务:{},执行耗时:{}秒...".format(name, dff_time))
        if dff_time > sec:
            self.log.warning("任务：{}，运行耗时：{}超过调度周期：{}".format(name, dff_time, sec))

    # 解析数据
    def parse_data(self, data, type_list):
        new_data = []
        for i in range(len(data)):
            row = data[i]
            new_data.append([])
            for ii in range(len(row)):
                if not type_list:
                    new_data[i].append(data[i][ii])
                    continue
                # print(data[i][ii],type(data[i][ii]))
                new_data[i].append(self.modify_type(type_list[ii], data[i][ii]))
                # print(data[i][ii],type(data[i][ii]))
            new_data[i] = tuple(new_data[i])
        return new_data

    # 转换成目的表字段类型
    def modify_type(self, _type, value):
        try:
            _type = _type.upper() if _type else None
            if not value:
                return None
            if _type in ('NUMBER', 'INT', 'SHORT', 'LONG', 'INTEGER',):
                return eval(str(value))
            if _type in ('DATETIME', 'TIMESTAMP', 'TIMESTAMP(0)', 'DATE', 'TIME', 'DATETIME2'):
                return timeEXT.str_to_datetime(value)
            if _type in ('REAL', 'FLOAT', 'DOUBLE', 'DECIMAL'):
                return float(value)
            else:
                return str(value)
        except Exception as e:
            self.log.debug("无法转换类型:{},返回字符串：{},异常：{}".format(_type, value, e))

    # 模块
    def load_module(self):
        g = {
                "main_self": self,
                'main': self,
                's7connect': self.config_window.s7_clinet_dict,
                's7_rt_value': self.s7_rt_value,

                }
        # 导入的模块
        modules = {m: eval(m) for m in MODULES if m[0] != '_'}
        modules.update({'sdk': self.config_window.sdk})
        g.update(modules)
        return g

    # 运行脚本
    def run_task(self, task_name = None, sec = 5):
        time_begin = time.process_time()
        self.log.debug("当前执行的任务是：%s" % task_name)
        file = os.path.join(os.path.splitext(sys.argv[0])[0], 'Scripts', task_name)
        if not os.path.exists(file):
            self.log.error("脚本：{},不存在,不执行此脚本".format(file))
            return
        with open(file, encoding = 'utf-8') as fileOpen:
            PyFile = fileOpen.read()
            if len(PyFile) >= 1:
                module = self.load_module()
                exec(PyFile, module)
        time_end = time.process_time()
        dff_time = time_end - time_begin
        self.log.debug("当前任务:{},执行耗时:{}秒...".format(task_name, dff_time))
        if dff_time > sec:
            self.log.warning("任务：{}，运行耗时：{}超过调度周期：{}".format(task_name, dff_time, sec))

    # 设置任务部件
    @error_info
    def set_widget(self, i, row):
        # 没有就增加行
        count = self.ui.task_table.rowCount()
        if i > (count - 1):
            self.ui.task_table.insertRow(i)

        name = row.get('Description')
        self.ui.task_table.setItem(i, 0, QTableWidgetItem(name))
        self.ui.task_table.item(i, 0).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.ui.task_table.item(i, 0).setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

        task_name = row.get('Name')
        self.ui.task_table.setItem(i, 1, QTableWidgetItem(task_name))
        self.ui.task_table.item(i, 1).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.ui.task_table.item(i, 1).setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

        cycle = row.get('Cycle')
        self.ui.task_table.setItem(i, 2, QTableWidgetItem(str(cycle)))
        self.ui.task_table.item(i, 2).setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

        value = row.get('AutoStart')
        widget1 = QCheckBox("")
        widget1.setChecked(value)
        self.ui.task_table.setCellWidget(i, 3, widget1)
        widget1.clicked.connect(lambda v: self.cell_widget_changed(i, v))

        widget2 = QPushButton("启动任务")
        self.ui.task_table.setCellWidget(i, 4, widget2)
        self.set_widget_color(self.ui.task_table.cellWidget(i, 4), 0)
        widget2.clicked.connect(lambda: self.pushbutton_clicked(i, 4))
        # 任务关闭
        widget3 = QPushButton("停止任务")
        self.ui.task_table.setCellWidget(i, 5, widget3)
        self.set_widget_color(self.ui.task_table.cellWidget(i, 5), 1)
        widget3.clicked.connect(lambda: self.pushbutton_clicked(i, 5))

        widget4 = QPushButton("移除任务")
        self.ui.task_table.setCellWidget(i, 6, widget4)
        self.set_widget_color(self.ui.task_table.cellWidget(i, 6), 2)
        widget4.clicked.connect(lambda: self.pushbutton_clicked(i, 6))

        # 设置信号
        if not self.item_signal_config:
            self.ui.task_table.itemChanged.connect(self.item_changed)
            self.item_signal_config = True

        if '.py' in task_name:
            return
        select_task_count = self.ui.select_task_list.count()
        # 设置任务选择列表
        task_name_list = [self.ui.select_task_list.itemText(i) for i in range(select_task_count)]
        if name not in task_name_list:
            self.ui.select_task_list.addItem(name)

    # 设置控件颜色
    @staticmethod
    def set_widget_color(obj, index = 0):
        if not obj:
            return
        data = {
                0: "background-color: rgba(85, 170, 0, 120);",
                1: "background-color: rgba(255, 0, 0, 120);",
                2: "background-color: rgba(255, 170, 0, 120);"
                }
        if isinstance(obj, QTableWidgetItem):
            obj.setForeground(QBrush(QColor(LOG_COLOR[index])))
            return
        obj.setStyleSheet(data[index])

    # 菜单显示配置窗口
    @check_local_db_password()
    def show_config_window(self, *args):
        show_window_top(self.config_window)

    # 菜单显示任务配置窗口
    @check_local_db_password()
    def show_task_config_window(self, *args):
        show_window_top(self.task_config_window)

    # 日志显示ui回调
    def log_callback(self, msg):
        messageList = re.findall('(\d+-\d+-\d+ \d+:\d+:\d+) (\d+) (.+)', msg)[0]
        # self.saveLogToDBSignal.emit(messageList)
        # 获取行数
        row = self.ui.log_view.rowCount()

        # 插入行
        self.ui.log_view.insertRow(row)
        level = messageList[1]
        color = LOG_COLOR.get(level, "Silver")
        for i in range(3):

            # 实例单元格
            item = QTableWidgetItem(messageList[i])
            if i == 2:  # 写提示
                item.setToolTip(messageList[i])
            # 设置背景色
            item.setForeground(QBrush(QColor(color)))
            self.ui.log_view.setItem(row, i, item)
            # 设置文字居中
            if i == 1:
                item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        # 选择行
        self.ui.log_view.selectRow(row)
        if self.ui.log_view.rowCount() >= 500:
            self.ui.log_view.setRowCount(0)

    # 初始日志窗口
    @staticmethod
    def init_log_widget(log_view):

        log_window = QDialog()
        log_window.setFixedSize(800, 600)
        grid = QGridLayout()
        grid.addWidget(log_view)
        log_window.setLayout(grid)
        log_window.setWindowTitle("日志")

        return log_window

    # 连接日志行
    def concat_log(self, path):
        with open(path, 'r') as f:
            data = f.readlines()
            for i, line in enumerate(data):
                color = get_log_color(line)
                msg = '<br/><span style=" color:{};">{} {}</span><br/>\n'.format(color, i, line)
                self.log_view.insertHtml(msg)

    # 打开日志文件
    def open_log_file(self, item = None):
        self.log_view.clear()
        if item:
            rows = item.row()
            cols = item.column()
            if cols != 2:
                return
            line = self.ui.log_view.item(rows, 2).text()
            self.log_view.setText(line)
        else:
            file = QFileDialog.getOpenFileName(None, '日志', './log', 'all file (*.*)')[0]
            if not os.path.exists(file):
                show_message("请选择要打开的日志文件")
                return
            self.concat_log(file)
        show_window_top(self.logWindow)


if __name__ == '__main__':
    from PyQt5.QtWidgets import QApplication
    import sys

    app = QApplication(sys.argv)
    gui = MainWindow()
    gui.show()
    # QApplication.setQuitOnLastWindowClosed(False)
    sys.exit(app.exec_())